/* 
 * Copyright (C) 2014 Ningbo Yuanqun Inc., All Rights Reserved. 
 */
package com.yuanqunsoft.alimigration.engine;

import static com.yuanqunsoft.alimigration.common.Helper.getRelativePath;
import static com.yuanqunsoft.alimigration.common.Helper.validExt;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.yuanqunsoft.alimigration.common.GlobalConfig;
import com.yuanqunsoft.alimigration.common.Helper;
import com.yuanqunsoft.alimigration.common.ParseEngineType;
import com.yuanqunsoft.alimigration.common.PropertiesUtil;
import com.yuanqunsoft.alimigration.engine.rule.factory.RuleParserFactory;
import com.yuanqunsoft.alimigration.engine.vo.RuleImpl;
import com.yuanqunsoft.alimigration.engine.vo.ScanResult;

/**
 * <p>
 * 一个 {@link ParseEngine} 在实例化时被指定了一个规则集，这个规则集将作为引擎的核心在其整个生命周期产生影响：<br>
 * 1.决定了这个引擎的类型，这个类型在 {@link ParseEngineType} 中被定义。 一种类型的引擎可以处理一种或若干种文件类型，这个映射关系通过common中的配置文件设置。<br>
 * 2.决定了这个引擎在处理一个文件时，进行哪些规则的扫描。
 * </p>
 * 
 * @author ShaoYu
 * @version 1.0
 */
final class ParseEngine {

    private final Logger logger = LogManager.getLogger(getClass());

    private static final int BUFFER = 2048;

    private Set<RuleImpl> ruleImpls;

    private String root;

    private Map<String, File> targetFiles;

    public ParseEngine(Set<RuleImpl> ruleImpls) {
        this.ruleImpls = ruleImpls;
        targetFiles = new HashMap<String, File>();
    }

    public List<ScanResult> scan(String scanFilePath) throws FileNotFoundException {

        Helper.checkStringNullorEmpty(scanFilePath, "scanFilePath");
        
        /*
         * 验证目标文件是否存在
         */
        String path = PropertiesUtil.getApplicationUploadDir().getAbsolutePath();
        File targetFile = new File(path + File.separator + scanFilePath);
        Helper.checkFileExist(targetFile);

        /*
         * 解压目标文件，并将待扫描文件置入文件集中，扫描
         */
        root = targetFile.getName().replaceAll("\\.zip|\\.jar|\\.war", "");
        logger.debug("<Init> Create and Traversal File.");
        traversal(targetFile);

        logger.debug("<Parse> Start! Target File Root Name is [" + root + "]");
        List<ScanResult> scanResults = new ArrayList<ScanResult>();
        Iterator<RuleImpl> iterator = ruleImpls.iterator();
        RuleParserFactory factory = new RuleParserFactory();
        while (iterator.hasNext()) {
            // TODO 考虑多线程提高扫描效率？
            RuleParser parser = factory.createRuleParser(iterator.next());
            ScanResult result = parser.parseFile(root, targetFiles);
            if (result != null) {
                scanResults.add(result);
            }
        }
        return scanResults;
    }

    /**
     * <p>
     * 验证并返回待扫描文件列表。如果文件不通过验证，返回一个空列表；如果通过验证，并且：<br>
     * 1.是非压缩文件，则返回仅包含该文件的列表；<br>
     * 2.是压缩文件，解压并返回符合扫描要求的文件列表
     * </p>
     * 
     * @param file
     *            待验证的文件
     */
    private void traversal(File file) {

        // 判断file是否为文件
        if (file.isFile()) {
            // 验证后缀名判断是否是几种支持的文件类型
            if (validExt(GlobalConfig.ENGINE_PROPERTIES_SUPPORT_SINGLE_FILE_TYPE, file.getName())) {
                targetFiles.put(getRelativePath(root, file), file);
                // logger.debug("<Traversal> Add File [" + getRelativePath(root, file) + "] to parse list.");
            } else if (validExt(GlobalConfig.ENGINE_PROPERTIES_SUPPORT_ZIP_FILE_TYPE, file.getName())) {
                // 否则，如果是zip文件,解压
                String unZipPath = file.getAbsolutePath().replaceAll("\\.zip|\\.jar|\\.war", "");
                traversal(extract(file, unZipPath));
            }
        } else {
            // 否则是文件夹
            for (File f : file.listFiles()) {
                // logger.debug("<Traversal> Travelsal [" + getRelativePath(root, f) + "]");
                traversal(f);
            }
        }

    }

    /**
     * <p>
     * 解压缩ZIP文件
     * </p>
     * 
     * @param file
     *            待解压的文件
     * @param unZipPath
     *            解压目录
     * @return
     */
    private File extract(File file, String unZipPath) {
        // logger.debug("<Unzip> Unzip file [" + getRelativePath(root, file) + "] to path [" + unZipPath + "]");
        File f = new File(unZipPath);
        if (f.exists() && f.isDirectory()) {
            deleteDirectory(f);
        }
        f.mkdir();
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
            ZipFile zip = new ZipFile(file);
            Enumeration<?> e = zip.entries();
            while (e.hasMoreElements()) {
                ZipEntry entry = (ZipEntry) e.nextElement();
                logger.debug("<Unzip> Extracting : " + entry.getName());
                if (entry.isDirectory()) {
                    File temp = new File(unZipPath + File.separator + entry.getName());
                    if (!temp.exists()) {
                        temp.mkdirs();
                    }
                } else {
                    bis = new BufferedInputStream(zip.getInputStream(entry));
                    int count;
                    byte[] data = new byte[BUFFER];
                    FileOutputStream fos = new FileOutputStream(unZipPath + File.separator + entry.getName());
                    bos = new BufferedOutputStream(fos, BUFFER);
                    while ((count = bis.read(data, 0, BUFFER)) != -1) {
                        bos.write(data, 0, count);
                    }
                    bos.flush();
                    bos.close();
                    bis.close();
                }
            }
            zip.close();
        } catch (IOException e) {
            logger.debug(e);
            e.printStackTrace();
        }
        logger.debug("<Unzip> Extracting Finished!");
        return f;
    }

    /**
     * <p>
     * 删除目录
     * </p>
     * 
     * @param f
     *            待删除的目录
     */
    private void deleteDirectory(File directory) {
        for (File f : directory.listFiles()) {
            if (f.isFile()) {
                f.delete();
                // logger.debug("<Unzip> Delete file [" + f.getAbsolutePath() + "].");
            } else {
                deleteDirectory(f);
            }
        }
        directory.delete();
        // logger.debug("<Unzip> Delete directory [" + directory.getAbsolutePath() + "] before extracting.");
    }

}
